-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RP: threading ##创建线程 finish :) #36
Conversation
Changes to be committed: new file: docs/cpp_thread.md new file: docs/img/cpp_thread/avoid_data_race.png new file: docs/img/cpp_thread/join.png new file: docs/img/cpp_thread/join_and_detach.png new file: docs/img/cpp_thread/jthread_stop.png new file: docs/img/cpp_thread/mem_order.png new file: docs/img/cpp_thread/mem_order_const.png new file: docs/img/cpp_thread/thread_state_transform.png
On branch cpp_thread
Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) Changes to be committed: deleted: docs/cpp_thread.md deleted: docs/img/cpp_thread/mem_order.png deleted: docs/img/cpp_thread/mem_order_const.png deleted: "docs/img/cpp_thread/\350\216\267\345\276\227\351\207\212\346\224\276\344\270\200\350\207\264\346\200\247.png" renamed: docs/img/cpp_thread/avoid_data_race.png -> docs/img/cpp_thread_avoid_data_race.png renamed: docs/img/cpp_thread/join.png -> docs/img/cpp_thread_join.png renamed: docs/img/cpp_thread/join_and_detach.png -> docs/img/cpp_thread_join_and_detach.png renamed: docs/img/cpp_thread/jthread_stop.png -> docs/img/cpp_thread_jthread_stop.png renamed: docs/img/cpp_thread/thread_state_transform.png -> docs/img/cpp_thread_thread_state_transform.png modified: docs/threading.md
Your branch is up to date with 'origin/main'. Changes to be committed: modified: docs/threading.md
threading ## 创建线程 |
为什么是英文句号?
按照小彭老师的“从cpp23学起”风格,jthread应该先于thread出现,作为初学者应当学习的,更好的api,并警告不要用thread。
然后介绍join,然后介绍stop_token,然后告诉他们jthread的析构是request_stop+join,然后介绍jthread的“空”对象和移动语义。
然后说明thread是一个历史遗留垃圾,不能自动join,有怎么怎么样的terminate问题,然后才给出thread_guard参考代码作为cpp20之前jthread的替代品,并提议永不使用赤膊的thread。
最后,再介绍detach,这会把当前thread变成一个空对象。detach是不应该使用的,是危险的,main退出前detach没有结束就就产生未定义行为,不异常安全等问题,强烈不推荐detach。
对于需要延迟执行的情况,小彭老师你不让detach,那怎么办?这时顺理成章推出线程池,完美。
无法顺畅的大口呼吸,是活着的最好证明
…---原始邮件---
发件人: ***@***.***>
发送时间: 2024年9月3日(周二) 下午3:18
收件人: ***@***.***>;
抄送: ***@***.***>;
主题: [parallel101/cppguidebook] RP: threading ##创建线程 finish :) (PR #36)
添加了 threaing ## 创建线程 的内容,下次希望继续上传 ## 线程间通信<mutex> 的内容
thread 的笔记我已经基本都写好了,会慢慢往上添加,请小彭老师多指教
You can view, comment on, or merge this pull request online at:
#36
Commit Summary
33ad4bc On branch cpp_thread
a4ed567 Merge pull request #1 from lzorn-lzorn/cpp_thread
84dc4cd On branch main
ab67795 Merge branch 'main' of https://github.com/lzorn-lzorn/cppguidebook
96580d4 On branch main
File Changes
(6 files)
A docs/img/cpp_thread_avoid_data_race.png (0)
A docs/img/cpp_thread_join.png (0)
A docs/img/cpp_thread_join_and_detach.png (0)
A docs/img/cpp_thread_jthread_stop.png (0)
A docs/img/cpp_thread_thread_state_transform.png (0)
M docs/threading.md (168)
Patch Links:
https://github.com/parallel101/cppguidebook/pull/36.patch
https://github.com/parallel101/cppguidebook/pull/36.diff
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
---原始邮件---
发件人: ***@***.***>
发送时间: 2024年9月3日(周二) 下午3:18
收件人: ***@***.***>;
抄送: ***@***.***>;
主题: [parallel101/cppguidebook] RP: threading ##创建线程 finish :) (PR #36)
添加了 threaing ## 创建线程 的内容,下次希望继续上传 ## 线程间通信<mutex> 的内容
thread 的笔记我已经基本都写好了,会慢慢往上添加,请小彭老师多指教
You can view, comment on, or merge this pull request online at:
#36
Commit Summary
33ad4bc On branch cpp_thread
a4ed567 Merge pull request #1 from lzorn-lzorn/cpp_thread
84dc4cd On branch main
ab67795 Merge branch 'main' of https://github.com/lzorn-lzorn/cppguidebook
96580d4 On branch main
File Changes
(6 files)
A docs/img/cpp_thread_avoid_data_race.png (0)
A docs/img/cpp_thread_join.png (0)
A docs/img/cpp_thread_join_and_detach.png (0)
A docs/img/cpp_thread_jthread_stop.png (0)
A docs/img/cpp_thread_thread_state_transform.png (0)
M docs/threading.md (168)
Patch Links:
https://github.com/parallel101/cppguidebook/pull/36.patch
https://github.com/parallel101/cppguidebook/pull/36.diff
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
另外,这里面对linux的task结构体实现细节大书特书,这些文本应该出现在linuxguidebook中,而不是cppguidebook。最重要的进程,线程,协程的区别,并行和并发的区别,适用场景等,却一笔带过了。如果是我的话,肯定会和python,java的线程横向对比一番。另外,既然提到了pthread,为什么不说明“pthread有一些stl thread做不到的功能”这一事实,并在(选读)中告知可以通过native_handle与pthread的额外功能联动,例如setaffinity。总之,你可能是自己私人的笔记粘贴上来了,然而你自己看得懂和让别人看懂差远了,哪天你把笔记写的别人也看得懂了,那才是真学懂了。
无法顺畅的大口呼吸,是活着的最好证明
…---原始邮件---
发件人: ***@***.***>
发送时间: 2024年9月3日(周二) 下午3:18
收件人: ***@***.***>;
抄送: ***@***.***>;
主题: [parallel101/cppguidebook] RP: threading ##创建线程 finish :) (PR #36)
添加了 threaing ## 创建线程 的内容,下次希望继续上传 ## 线程间通信<mutex> 的内容
thread 的笔记我已经基本都写好了,会慢慢往上添加,请小彭老师多指教
You can view, comment on, or merge this pull request online at:
#36
Commit Summary
33ad4bc On branch cpp_thread
a4ed567 Merge pull request #1 from lzorn-lzorn/cpp_thread
84dc4cd On branch main
ab67795 Merge branch 'main' of https://github.com/lzorn-lzorn/cppguidebook
96580d4 On branch main
File Changes
(6 files)
A docs/img/cpp_thread_avoid_data_race.png (0)
A docs/img/cpp_thread_join.png (0)
A docs/img/cpp_thread_join_and_detach.png (0)
A docs/img/cpp_thread_jthread_stop.png (0)
A docs/img/cpp_thread_thread_state_transform.png (0)
M docs/threading.md (168)
Patch Links:
https://github.com/parallel101/cppguidebook/pull/36.patch
https://github.com/parallel101/cppguidebook/pull/36.diff
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
确实是我以前写好的笔记,所以只放了放了一小点先看看。我以后再改一下吧,把顺序和内容的方向再修正一下。感谢:) |
Your branch is up to date with 'origin/main'. Changes to be committed: modified: docs/threading.md
Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) Changes to be committed: modified: docs/threading.md
|
||
同时可调用对象还可以有返回, 但是jthread会忽略这个返回值。 如果想接住这个返回值需要借助 `std::future`。 见后。 | ||
|
||
jthread支持空初始化 `std::jthread jt;` 此时 `jt` 只是一个占位符, 并不是一个线程。如果后续需要分配任务, 使用jthread的移动语义。(jthread不能拷贝) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jthread支持空初始化 `std::jthread jt;` 此时 `jt` 只是一个占位符, 并不是一个线程。如果后续需要分配任务, 使用jthread的移动语义。(jthread不能拷贝) | |
jthread可以捕获参数,就像bind一样,也存在不能捕获引用的痛点。通常情况下使用的是 jthread 基于单个可调用对象的功能,而不会使用捕获参数的功能,如需捕获任何参数,可以使用更可读性的lambda表达式的 `[=]`,例如: | |
```cpp | |
void func(int i, int j); | |
int i = 1, j = 2; | |
jthread t(func, i, j); | |
// 等价于: | |
jthread t([=] { func(i, j); }); | |
\`\`\` | |
`jthread` 就和 `unique_ptr` 一样,只支持移动,不支持拷贝,且具有一个空状态(类比于unique_ptr的nullptr)。 | |
通过默认构造函数创建的 `jthread` 就处于这种空状态,只是一个占位符,并不持有线程资源,等待你的后续构造和存入。 | |
好处是,如需延迟初始化,可以先把jthread构造为空状态,之后构造出jthread时,再使用jthread的移动赋值函数,往里面赋值。 |
threading jthread 1. 初始化 2. 结束线程 |
实际上对于C++20的jthread而言, 会在其销毁的时候自动调用 `join()`. 但如果你使用的旧版本 `std::thread` 则需要手动的调用 `join()` 或者 `detach()`, 此时你应该通过thread_guard类保证在作用域结束之后自动调用的`join()` | ||
|
||
> #### `joinable()` | ||
> 初始化子线程 `t` 后, 该子线程自动就是 joinable 的, 也就是 `t.joinable()` 的值是 `true`. 换句话说 `t.joinable()` 等于 `true` 的条件就是该线程有一个与之相关联的线程(父线程)。 当其detach之后也就独立于父线程运行, 此时的 `t.joinable()` 就返回 `false`. 在官方的描述中, `t.joinable()` 返回 `true` 则意味着可以通过 `get_id()` 得到这个线程的唯一标识. 但是当detach之后这个标识会返回0。换句话说, 一旦将一个线程detach之后就再也无法直接控制这个线程, 只能按照其原本的逻辑运行直至结束。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
其实joinable()实际上就是is_not_null(),这个名字是误导性的,detach后jthread变为空状态,刚好就是joinable()==false,detach后getid返回0就是因为这个,detach后的jthread和默认初始化的jthread是一样的。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“detach后jthread变为空状态”,准确来说是 detach 是让线程对象放弃了线程的所有权,线程对象不再和线程资源关联,线程对象自然为空。我们此时可以继续操作空的线程对象。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
补充:detach和join返回后,jthread对象都会进入这种空状态。
## jthread | ||
### 使用C++20的 `std::jthread` | ||
秉承学新不学旧的思路, 先介绍C++20提供的 `std::jthead`, 如果由于各种限制以至于读者无法使用jthread 也无需担心。 C++11提供的 `std::thread` 是 `std::jthead` 的阉割版。 你可以无缝的回到过去。 | ||
### 初始化`std::jthread` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不应该如此,应当先行介绍 std::thread
,如果要认真聊 std::jthread
则没有单独聊 std::thread
的必要。
std::jthread
相比于 C++11 引入的 std::thread
,只是多了两个功能:
-
RAII 管理:在析构时自动调用 join()。
-
线程停止功能:线程的取消/停止。
同时,我建议约定格式,中英空格。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
错误的,用std::thread这种行为和赤膊new-delete没有任何区别,只不过是内存泄漏变成了响亮的terminate。
std::thread t([]{}); // 赤膊new
if (xxx) throw;
t.join(); // 赤膊delete
那么将会直接terminate,而jthread能自动request_stop并join。
我知道你可能担心request_stop讲不明白,那么也可以先按下不表,等后面讲到stop_token了再提起jthread的这一额外功能。
秉承学新不学旧的思路, 先介绍C++20提供的 `std::jthead`, 如果由于各种限制以至于读者无法使用jthread 也无需担心。 C++11提供的 `std::thread` 是 `std::jthead` 的阉割版。 你可以无缝的回到过去。 | ||
### 初始化`std::jthread` | ||
C++20封装好的线程对象jthread接受一个**可调用对象(callable)**, 换句话说就是重载了 `()` 运算符的对象, 它可以是一个函数, 重载了 `()` 的类又或者是一个lambda表达式 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不对,你对可调用对象的理解有很大问题,同时也不要求一定是 ()
这种调用形式,如成员指针。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
可以说:任何可以被std::invoke调用的。例如成员函数指针&Class::memfn
不支持()
调用,但std::invoke(&Class::memfn, this)
就支持,因而std::jthread j(&Class::memfn, this)
也是支持的。当然,支持()
的肯定也都支持std::invoke()
,是一个超集关系。
C++20封装好的线程对象jthread接受一个**可调用对象(callable)**, 换句话说就是重载了 `()` 运算符的对象, 它可以是一个函数, 重载了 `()` 的类又或者是一个lambda表达式 | ||
|
||
同时jthread的构造函数本身就是有invoke功能, 所以第一个参数是可调用对象, 后面的参数直接跟上可调用对象的参数即可。 此时需要注意一点, 如果可调用对线的参数中有引用传递, 则需要用 `std::ref` 或者 `std::cref` 包装。 因为默认是按值或移动传递。 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“因为默认是按值或移动传递”,此处描述极其不准确,这里主要涉及到内部实现的问题。
void f(int, int& a) {
std::cout << &a << '\n';
}
int main() {
int n = 1;
std::cout << &n << '\n';
std::thread t { f, 3, std::ref(n) };
t.join();
}
代码 void f(int, int&)
如果不使用 std::ref
并不会和 void f(int, const int&)
一样只是多了复制,而是会产生编译错误,这是因为 std::thread
内部会将保有的参数副本转换为右值表达式进行传递,这是为了那些只支持移动的类型,左值引用没办法引用右值表达式,所以产生编译错误。
“将保有的参数副本”,其实说的是作为 std::thread 构造参数的传递的时候会先decay-copy。
|
||
同时可调用对象还可以有返回, 但是jthread会忽略这个返回值。 如果想接住这个返回值需要借助 `std::future`。 见后。 | ||
|
||
jthread支持空初始化 `std::jthread jt;` 此时 `jt` 只是一个占位符, 并不是一个线程。如果后续需要分配任务, 使用jthread的移动语义。(jthread不能拷贝) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不应该使用所谓的“占位符”这种用词,此处不对。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这样呢
jthread支持空初始化 `std::jthread jt;` 此时 `jt` 只是一个占位符, 并不是一个线程。如果后续需要分配任务, 使用jthread的移动语义。(jthread不能拷贝) | |
jthread支持空初始化 `std::jthread jt;` 此时 `jt` 只是一个未绑定线程的空对象, 并不是一个线程。如果后续需要分配任务, 使用jthread的移动语义。(jthread不能拷贝) |
|
||
|
||
## 线程的结束方式 | ||
线程的结束方式有两种:`join()` 和 `detach()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
此处用词错误,join
和 detach
不是“线程的结束方式”,不符合自然语言,改为“执行策略”会更加合适。
|
||
但是同时也要知道等待线程join可能是一件非常耗时的时候, 所以一般会在最后join。 但是detach()可以在一开始就进行, 因为反正也不需要等他返回。 | ||
|
||
如果既不调用 `join()` 也不调用 `detach()`. 当线程对象的析构函数被调用时(通常在离开作用域或显式销毁时),由于线程对象仍然和一个活动的线程相关联,这会导致调用 `std::terminate()`,终止整个程序。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
应当强调是 std::thread
析构函数会调用 joinable()
判断当前线程对象是否有活跃线程,如果有,则直接调用 std::terminate()
终止程序,因为这不符合逻辑,线程还活着,线程对象还要析构。
剩下还有很多用词问题,自己看。 |
添加了 threaing ## 创建线程 的内容,下次希望继续上传 ## 线程间通信
<mutex>
的内容thread 的笔记我已经基本都写好了,会慢慢往上添加,请小彭老师多指教